﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace DungeonGenerator.Java
{
    using System.ComponentModel;

    public enum Tile
    {
        [Description("_")]
        Unused,
        [Description(".")]
        DirtWall,
        DirtFloor,
        StoneWall,
        Corridor,
        Door,
        Upstairs,
        Downstairs,
        Chest
    }

    public class JDungeon
    {
      
            //max size of the map
            int xmax = 80; //80 columns
            int ymax = 25; //25 rows

            //size of the map
            int xsize = 0;
            int ysize = 0;

            //number of "objects" to generate on the map
            int objects = 0;

            //define the %chance to generate either a room or a corridor on the map
            //BTW, rooms are 1st priority so actually it's enough to just define the chance
            //of generating a room
            int chanceRoom = 75;

            //our map
            Tile[] dungeon_map = { };
            IRandomize _rnd;

        readonly Action<string> _logger;

        //misc. messages to print
            String msgXSize = "X size of dungeon: \t";
            String msgYSize = "Y size of dungeon: \t";
            String msgMaxObjects = "max # of objects: \t";
            String msgNumObjects = "# of objects made: \t";


            
            
            public JDungeon(IRandomize rnd,Action<string> logger)
            {
                _rnd = rnd;
                _logger = logger;
            }

        public Tile GetCellType(int x, int y)
            {
                try
                {
                    return dungeon_map[x + xsize * y];
                }
                catch (IndexOutOfRangeException ex)
                {
                    new { x, y }.Dump("exceptional");
                    throw;
                }

            }
            public int Corridors
            {
                get; private set;
            }
            public int getRand(int min, int max)
            {
                return _rnd.Next(min, max);
            }
            public  bool makeCorridor(int x, int y, int length, int direction)
            {
                //define the dimensions of the corridor (er.. only the width and height..)
                int len = getRand(2, length);
                var floor = Tile.Corridor;
                int dir = 0;
                if (direction > 0 && direction < 4) dir = direction;

                int xtemp = 0;
                int ytemp = 0;

                switch (dir)
                {
                    case 0:
                        //north
                        //check if there's enough space for the corridor
                        //start with checking it's not out of the boundaries
                        if (x < 0 || x > xsize) return false;
                        else xtemp = x;

                        //same thing here, to make sure it's not out of the boundaries
                        for (ytemp = y; ytemp > (y - len); ytemp--)
                        {
                            if (ytemp < 0 || ytemp > ysize) return false; //oh boho, it was!
                            if (GetCellType(xtemp, ytemp) != Tile.Unused) return false;
                        }

                        //if we're still here, let's start building
                        Corridors++;
                        for (ytemp = y; ytemp > (y - len); ytemp--)
                        {
                            this.SetCell(xtemp, ytemp, floor);
                        }
                        break;
                    case 1:
                        //east
                        if (y < 0 || y > ysize) return false;
                        else ytemp = y;

                        for (xtemp = x; xtemp < (x + len); xtemp++)
                        {
                            if (xtemp < 0 || xtemp > xsize) return false;
                            if (GetCellType(xtemp, ytemp) != Tile.Unused) return false;
                        }
                        Corridors++;
                        for (xtemp = x; xtemp < (x + len); xtemp++)
                        {
                            this.SetCell(xtemp, ytemp, floor);
                        }
                        break;
                    case 2:
                        //south
                        if (x < 0 || x > xsize) return false;
                        else xtemp = x;

                        for (ytemp = y; ytemp < (y + len); ytemp++)
                        {
                            if (ytemp < 0 || ytemp > ysize) return false;
                            if (GetCellType(xtemp, ytemp) != Tile.Unused) return false;
                        }
                        Corridors++;
                        for (ytemp = y; ytemp < (y + len); ytemp++)
                        {
                            this.SetCell(xtemp, ytemp, floor);
                        }
                        break;
                    case 3:
                        //west
                        if (ytemp < 0 || ytemp > ysize) return false;
                        else ytemp = y;

                        for (xtemp = x; xtemp > (x - len); xtemp--)
                        {
                            if (xtemp < 0 || xtemp > xsize) return false;
                            if (GetCellType(xtemp, ytemp) != Tile.Unused) return false;
                        }
                        Corridors++;
                        for (xtemp = x; xtemp > (x - len); xtemp--)
                        {
                            this.SetCell(xtemp, ytemp, floor);
                        }
                        break;
                }

                //woot, we're still here! let's tell the other guys we're done!!
                return true;
            }
            

          
            public bool MakeRoom(int x, int y, int xlength, int ylength, int direction)
            {
                //define the dimensions of the room, it should be at least 4x4 tiles (2x2 for walking on, the rest is walls)
                int xlen = getRand(4, xlength);
                int ylen = getRand(4, ylength);

                //the tile type it's going to be filled with

                const Tile floor = Tile.DirtFloor; 

                var wall = Tile.DirtWall;
                //choose the way it's pointing at
                int dir = 0;
                if (direction > 0 && direction < 4) dir = direction;

                switch (dir)
                {
                    case 0:
                        //north
                        //Check if there's enough space left for it
                        for (int ytemp = y; ytemp > (y - ylen); ytemp--)
                        {
                            if (ytemp < 0 || ytemp > ysize) return false;
                            for (int xtemp = (x - xlen / 2); xtemp < (x + (xlen + 1) / 2); xtemp++)
                            {
                                if (xtemp < 0 || xtemp > xsize) return false;
                                if (GetCellType(xtemp, ytemp) != Tile.Unused) return false; //no space left...
                            }
                        }
                        _logger(
                            String.Format(
                                "Making room:int x={0}, int y={1}, int xlength={2}, int ylength={3}, int direction={4}",
                                x,
                                y,
                                xlength,
                                ylength,
                                direction));
                        //we're still here, build
                        for (int ytemp = y; ytemp > (y - ylen); ytemp--)
                        {
                            for (int xtemp = (x - xlen / 2); xtemp < (x + (xlen + 1) / 2); xtemp++)
                            {
                                //start with the walls
                                if (xtemp == (x - xlen / 2)) this.SetCell(xtemp, ytemp, wall);
                                else if (xtemp == (x + (xlen - 1) / 2)) this.SetCell(xtemp, ytemp, wall);
                                else if (ytemp == y) this.SetCell(xtemp, ytemp, wall);
                                else if (ytemp == (y - ylen + 1)) this.SetCell(xtemp, ytemp, wall);
                                //and then fill with the floor
                                else this.SetCell(xtemp, ytemp, floor);
                            }
                        }
                        break;
                    case 1:
                        //east
                        for (int ytemp = (y - ylen / 2); ytemp < (y + (ylen + 1) / 2); ytemp++)
                        {
                            if (ytemp < 0 || ytemp > ysize) return false;
                            for (int xtemp = x; xtemp < (x + xlen); xtemp++)
                            {
                                if (xtemp < 0 || xtemp > xsize) return false;
                                if (GetCellType(xtemp, ytemp) != Tile.Unused) return false;
                            }
                        }
                        _logger(
                           String.Format(
                               "Making room:int x={0}, int y={1}, int xlength={2}, int ylength={3}, int direction={4}",
                               x,
                               y,
                               xlength,
                               ylength,
                               direction));
                        for (int ytemp = (y - ylen / 2); ytemp < (y + (ylen + 1) / 2); ytemp++)
                        {
                            for (int xtemp = x; xtemp < (x + xlen); xtemp++)
                            {

                                if (xtemp == x) this.SetCell(xtemp, ytemp, wall);
                                else if (xtemp == (x + xlen - 1)) this.SetCell(xtemp, ytemp, wall);
                                else if (ytemp == (y - ylen / 2)) this.SetCell(xtemp, ytemp, wall);
                                else if (ytemp == (y + (ylen - 1) / 2)) this.SetCell(xtemp, ytemp, wall);

                                else this.SetCell(xtemp, ytemp, floor);
                            }
                        }
                        break;
                    case 2:
                        //south
                        for (int ytemp = y; ytemp < (y + ylen); ytemp++)
                        {
                            if (ytemp < 0 || ytemp > ysize) return false;
                            for (int xtemp = (x - xlen / 2); xtemp < (x + (xlen + 1) / 2); xtemp++)
                            {
                                if (xtemp < 0 || xtemp > xsize) return false;
                                if (GetCellType(xtemp, ytemp) != Tile.Unused) return false;
                            }
                        }
                        _logger(
                           String.Format(
                               "Making room:int x={0}, int y={1}, int xlength={2}, int ylength={3}, int direction={4}",
                               x,
                               y,
                               xlength,
                               ylength,
                               direction));
                        for (int ytemp = y; ytemp < (y + ylen); ytemp++)
                        {
                            for (int xtemp = (x - xlen / 2); xtemp < (x + (xlen + 1) / 2); xtemp++)
                            {

                                if (xtemp == (x - xlen / 2)) this.SetCell(xtemp, ytemp, wall);
                                else if (xtemp == (x + (xlen - 1) / 2)) this.SetCell(xtemp, ytemp, wall);
                                else if (ytemp == y) this.SetCell(xtemp, ytemp, wall);
                                else if (ytemp == (y + ylen - 1)) this.SetCell(xtemp, ytemp, wall);

                                else this.SetCell(xtemp, ytemp, floor);
                            }
                        }
                        break;
                    case 3:
                        //west
                        for (int ytemp = (y - ylen / 2); ytemp < (y + (ylen + 1) / 2); ytemp++)
                        {
                            if (ytemp < 0 || ytemp > ysize) return false;
                            for (int xtemp = x; xtemp > (x - xlen); xtemp--)
                            {
                                if (xtemp < 0 || xtemp > xsize) return false;
                                if (GetCellType(xtemp, ytemp) != Tile.Unused) return false;
                            }
                        }
                        _logger(
                           String.Format(
                               "Making room:int x={0}, int y={1}, int xlength={2}, int ylength={3}, int direction={4}",
                               x,
                               y,
                               xlength,
                               ylength,
                               direction));
                        for (int ytemp = (y - ylen / 2); ytemp < (y + (ylen + 1) / 2); ytemp++)
                        {
                            for (int xtemp = x; xtemp > (x - xlen); xtemp--)
                            {

                                if (xtemp == x) this.SetCell(xtemp, ytemp, wall);
                                else if (xtemp == (x - xlen + 1)) this.SetCell(xtemp, ytemp, wall);
                                else if (ytemp == (y - ylen / 2)) this.SetCell(xtemp, ytemp, wall);
                                else if (ytemp == (y + (ylen - 1) / 2)) this.SetCell(xtemp, ytemp, wall);

                                else this.SetCell(xtemp, ytemp, floor);
                            }
                        }
                        break;
                }

                //yay, all done
                return true;
            }

            public Tile[] GetDungeon()
            {
                return dungeon_map;
            }


            public char GetCellTile(int x, int y)
            {
                switch (GetCellType(x, y))
                {
                    case Tile.Unused:
                        return '_';
                    case Tile.DirtWall:
                        return '+';
                    case Tile.DirtFloor:
                        return '.';
                    case Tile.StoneWall:
                        return 'O';
                    case Tile.Corridor:
                        return '#';
                    case Tile.Door:
                        return 'D';
                    case Tile.Upstairs:
                        return '<';
                    case Tile.Downstairs:
                        return '>';
                    case Tile.Chest:
                        return '*';
                    default:
                        throw new ArgumentOutOfRangeException("x,y");
                }
            }

            //used to print the map on the screen
            public void ShowDungeon()
            {
                for (int y = 0; y < ysize; y++)
                {
                    for (int x = 0; x < xsize; x++)
                    {
                        
                        Console.Write(GetCellTile(x, y));
                    }
                    if (xsize <= xmax) Console.WriteLine();
                }
            }

            //and here's the one generating the whole map
            public bool CreateDungeon(int inx, int iny, int inobj)
            {
                if (inobj < 1) objects = 10;
                else objects = inobj;

                //adjust the size of the map, if it's smaller or bigger than the limits
                if (inx < 3) xsize = 3;
                else if (inx > xmax) xsize = xmax;
                else xsize = inx;

                if (iny < 3) ysize = 3;
                else if (iny > ymax) ysize = ymax;
                else ysize = iny;

                Console.WriteLine(msgXSize + xsize);
                Console.WriteLine(msgYSize + ysize);
                Console.WriteLine(msgMaxObjects + objects);

                //redefine the map var, so it's adjusted to our new map size
                dungeon_map = new Tile[xsize * ysize];

                //start with making the "standard stuff" on the map
                for (int y = 0; y < ysize; y++)
                {
                    for (int x = 0; x < xsize; x++)
                    {
                        //ie, making the borders of unwalkable walls
                        if (y == 0) this.SetCell(x, y, Tile.StoneWall);
                        else if (y == ysize - 1) this.SetCell(x, y, Tile.StoneWall);
                        else if (x == 0) this.SetCell(x, y, Tile.StoneWall);
                        else if (x == xsize - 1) this.SetCell(x, y, Tile.StoneWall);

                        //and fill the rest with dirt
                        else this.SetCell(x, y, Tile.Unused);
                    }
                }

                /*******************************************************************************
                And now the code of the random-map-generation-algorithm begins!
                *******************************************************************************/

                //start with making a room in the middle, which we can start building upon
                this.MakeRoom(xsize / 2, ysize / 2, 8, 6, getRand(0, 4)); //getrand saken f????r att slumpa fram riktning p?? rummet

                //keep count of the number of "objects" we've made
                int currentFeatures = 1; //+1 for the first room we just made

                //then we sart the main loop
                for (int countingTries = 0; countingTries < 1000; countingTries++)
                {
                    //check if we've reached our quota
                    if (currentFeatures == objects)
                    {
                        break;
                    }

                    //start with a random wall
                    int newx = 0;
                    int xmod = 0;
                    int newy = 0;
                    int ymod = 0;
                    int validTile = -1;
                    //1000 chances to find a suitable object (room or corridor)..
                    //(yea, i know it's kinda ugly with a for-loop... -_-')
                    for (int testing = 0; testing < 1000; testing++)
                    {
                        newx = getRand(1, xsize - 1);
                        newy = getRand(1, ysize - 1);
                        validTile = -1;
                        //Console.WriteLine("tempx: " + newx + "\ttempy: " + newy);
                        if (GetCellType(newx, newy) == Tile.DirtWall || GetCellType(newx, newy) == Tile.Corridor)
                        {
                            //check if we can reach the place
                            if (GetCellType(newx, newy + 1) == Tile.DirtFloor || GetCellType(newx, newy + 1) == Tile.Corridor)
                            {
                                validTile = 0; //
                                xmod = 0;
                                ymod = -1;
                            }
                            else if (GetCellType(newx - 1, newy) == Tile.DirtFloor || GetCellType(newx - 1, newy) == Tile.Corridor)
                            {
                                validTile = 1; //
                                xmod = +1;
                                ymod = 0;
                            }
                            else if (GetCellType(newx, newy - 1) == Tile.DirtFloor || GetCellType(newx, newy - 1) == Tile.Corridor)
                            {
                                validTile = 2; //
                                xmod = 0;
                                ymod = +1;
                            }
                            else if (GetCellType(newx + 1, newy) == Tile.DirtFloor || GetCellType(newx + 1, newy) == Tile.Corridor)
                            {
                                validTile = 3; //
                                xmod = -1;
                                ymod = 0;
                            }

                            //check that we haven't got another door nearby, so we won't get alot of openings besides
                            //each other
                            if (validTile > -1)
                            {
                                if (GetCellType(newx, newy + 1) == Tile.Door) // north
                                    validTile = -1;
                                else if (GetCellType(newx - 1, newy) == Tile.Door) // east
                                    validTile = -1;
                                else if (GetCellType(newx, newy - 1) == Tile.Door) // south
                                    validTile = -1;
                                else if (GetCellType(newx + 1, newy) == Tile.Door) // west
                                    validTile = -1;
                            }

                            //if we can, jump out of the loop and continue with the rest
                            if (validTile > -1) break;
                        }
                    }
                    if (validTile > -1)
                    {
                        //choose what to build now at our newly found place, and at what direction
                        int feature = getRand(0, 100);
                        if (feature <= chanceRoom)
                        { //a new room
                            if (this.MakeRoom((newx + xmod), (newy + ymod), 8, 6, validTile))
                            {
                                currentFeatures++; //add to our quota

                                //then we mark the wall opening with a door
                                this.SetCell(newx, newy, Tile.Door);

                                //clean up infront of the door so we can reach it
                                this.SetCell((newx + xmod), (newy + ymod), Tile.DirtFloor);
                            }
                        }
                        else if (feature >= chanceRoom)
                        { //new corridor
                            if (makeCorridor((newx + xmod), (newy + ymod), 6, validTile))
                            {
                                //same thing here, add to the quota and a door
                                currentFeatures++;

                                this.SetCell(newx, newy, Tile.Door);
                            }
                        }
                    }
                }


                /*******************************************************************************
                All done with the building, let's finish this one off
                *******************************************************************************/
                AddSprinkles();



                //all done with the map generation, tell the user about it and finish
                Console.WriteLine(msgNumObjects + currentFeatures);

                return true;
            }

            //setting a tile's type
            void SetCell(int x, int y, Tile celltype)
            {
                dungeon_map[x + xsize * y] = celltype;
            }

            ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
            void AddSprinkles()
            {
                //sprinkle out the bonusstuff (stairs, chests etc.) over the map
                var newx = 0;
                var newy = 0;
                int ways = 0; //from how many directions we can reach the random spot from
                int state = 0; //the state the loop is in, start with the stairs
                while (state != 10)
                {
                    for (int testing = 0; testing < 1000; testing++)
                    {
                        newx = getRand(1, xsize - 1);
                        newy = getRand(1, ysize - 2); //cheap bugfix, pulls down newy to 0<y<24, from 0<y<25

                        //Console.WriteLine("x: " + newx + "\ty: " + newy);
                        ways = 4; //the lower the better

                        //check if we can reach the spot
                        if (GetCellType(newx, newy + 1) == Tile.DirtFloor || GetCellType(newx, newy + 1) == Tile.Corridor)
                        {
                            //north
                            if (GetCellType(newx, newy + 1) != Tile.Door)
                                ways--;
                        }
                        if (GetCellType(newx - 1, newy) == Tile.DirtFloor || GetCellType(newx - 1, newy) == Tile.Corridor)
                        {
                            //east
                            if (GetCellType(newx - 1, newy) != Tile.Door)
                                ways--;
                        }
                        if (GetCellType(newx, newy - 1) == Tile.DirtFloor || GetCellType(newx, newy - 1) == Tile.Corridor)
                        {
                            //south
                            if (GetCellType(newx, newy - 1) != Tile.Door)
                                ways--;
                        }
                        if (GetCellType(newx + 1, newy) == Tile.DirtFloor || GetCellType(newx + 1, newy) == Tile.Corridor)
                        {
                            //west
                            if (GetCellType(newx + 1, newy) != Tile.Door)
                                ways--;
                        }

                        if (state == 0)
                        {
                            if (ways == 0)
                            {
                                //we're in state 0, let's place a "upstairs" thing
                                this.SetCell(newx, newy, Tile.Upstairs);
                                state = 1;
                                break;
                            }
                        }
                        else if (state == 1)
                        {
                            if (ways == 0)
                            {
                                //state 1, place a "downstairs"
                                this.SetCell(newx, newy, Tile.Downstairs);
                                state = 10;
                                break;
                            }
                        }
                    }
                }
            }
        }
   
}
